﻿using System;


namespace BizElements.BusinessLayer
{
    /// <summary>
    /// Represents a single or a set of values with optional name and description. 
    /// Supports all <see cref="NamedValueSetType"/> options.
    /// </summary>
    /// <typeparam name="T">The type of values contained in the set.</typeparam>
    [Serializable]
    public sealed class NamedValueSet<T> : INamedValueSet<T>
    {
        #region Fields.

        NamedValueSetType valueSetType;
        T[] values;
        string key;
        string name;
        string description;

        #endregion

        #region Constructors and static initializers.

        private NamedValueSet()
        { }

        /// <summary>
        /// Initializes a new instance of the <b>NamedValueSet</b> class.
        /// </summary>
        /// <param name="valueSetType">Type of data structure contained in the current instance.</param>
        /// <param name="values">Values contained in the set.</param>
        /// <param name="name">Name of the set.</param>
        /// <param name="description">Description of the set.</param>
        public NamedValueSet(NamedValueSetType valueSetType, T[] values, string name, string description)
        {
            this.valueSetType = valueSetType;
            this.values = values;
            this.name = name;
            this.description = description;
        }

        /// <summary>
        /// Creates a <b>NamedValueSet</b> object which represents a single value.
        /// </summary>
        /// <param name="singleValue">Single value contained in the set.</param>
        /// <param name="name">Name of the set.</param>
        /// <remarks>The method automatically sets the <see cref="NamedValueSetType.SingleValue"/> type.</remarks>
        public static NamedValueSet<T> NewSingleValue(T singleValue, string name)
        {
            NamedValueSet<T> valueSet = new NamedValueSet<T>();
            valueSet.valueSetType = NamedValueSetType.SingleValue;
            valueSet.values = new T[] { singleValue };
            valueSet.name = name;

            return valueSet;
        }

        /// <summary>
        /// Creates a <b>NamedValueSet</b> object which represents a single value.
        /// </summary>
        /// <param name="singleValue">Single value contained in the set.</param>
        /// <param name="name">Name of the set.</param>
        /// <param name="description">Description of the set.</param>
        /// <remarks>The method automatically sets the <see cref="NamedValueSetType.SingleValue"/> type.</remarks>
        public static NamedValueSet<T> NewSingleValue(T singleValue, string name, string description)
        {
            NamedValueSet<T> valueSet = new NamedValueSet<T>();
            valueSet.valueSetType = NamedValueSetType.SingleValue;
            valueSet.values = new T[] { singleValue };
            valueSet.name = name;
            valueSet.description = description;

            return valueSet;
        }

        /// <summary>
        /// Creates a <b>NamedValueSet</b> object which represents an interval of values defined by begin and end values.
        /// </summary>
        /// <param name="intervalBeginValue">Begin value of the interval represented by the set.</param>
        /// <param name="intervalEndValue">End value of the interval represented by the set.</param>
        /// <param name="name">Name of the set.</param>
        /// <remarks>The method automatically sets the <see cref="NamedValueSetType.Interval"/> type.</remarks>
        public static NamedValueSet<T> NewInterval(T intervalBeginValue, T intervalEndValue, string name)
        {
            NamedValueSet<T> valueSet = new NamedValueSet<T>();
            valueSet.valueSetType = NamedValueSetType.Interval;
            valueSet.values = new T[] { intervalBeginValue, intervalEndValue };
            valueSet.name = name;

            return valueSet;
        }

        /// <summary>
        /// Creates a <b>NamedValueSet</b> object which represents an interval of values defined by begin and end values.
        /// </summary>
        /// <param name="intervalBeginValue">Begin value of the interval represented by the set.</param>
        /// <param name="intervalEndValue">End value of the interval represented by the set.</param>
        /// <param name="name">Name of the set.</param>
        /// <param name="description">Description of the set.</param>
        /// <remarks>The method automatically sets the <see cref="NamedValueSetType.Interval"/> type.</remarks>
        public static NamedValueSet<T> NewInterval(T intervalBeginValue, T intervalEndValue, string name, string description)
        {
            NamedValueSet<T> valueSet = new NamedValueSet<T>();
            valueSet.valueSetType = NamedValueSetType.Interval;
            valueSet.values = new T[] { intervalBeginValue, intervalEndValue };
            valueSet.name = name;
            valueSet.description = description;

            return valueSet;
        }

        /// <summary>
        /// Creates a <b>NamedValueSet</b> object which represents a list of values.
        /// </summary>
        /// <param name="listOfValues">List of values contained in the set.</param>
        /// <param name="name">Name of the set.</param>
        /// <remarks>The method automatically sets the <see cref="NamedValueSetType.ListOfValues"/> type.</remarks>
        public static NamedValueSet<T> NewNamedListOfValues(T[] listOfValues, string name)
        {
            NamedValueSet<T> valueSet = new NamedValueSet<T>();
            valueSet.valueSetType = NamedValueSetType.ListOfValues;
            valueSet.values = listOfValues;
            valueSet.name = name;

            return valueSet;
        }

        /// <summary>
        /// Creates a <b>NamedValueSet</b> object which represents a list of values.
        /// </summary>
        /// <param name="listOfValues">List of values contained in the set.</param>
        /// <param name="name">Name of the set.</param>
        /// <param name="description">Description of the set.</param>
        /// <remarks>The method automatically sets the <see cref="NamedValueSetType.ListOfValues"/> type.</remarks>
        public static NamedValueSet<T> NewNamedListOfValues(T[] listOfValues, string name, string description)
        {
            NamedValueSet<T> valueSet = new NamedValueSet<T>();
            valueSet.valueSetType = NamedValueSetType.ListOfValues;
            valueSet.values = listOfValues;
            valueSet.name = name;
            valueSet.description = description;

            return valueSet;
        }

        #endregion

        #region INamedValueSet.

        /// <summary>
        /// Gets the type of data structure contained in the current instance.
        /// </summary>
        public NamedValueSetType ValueSetType
        {
            get { return this.valueSetType; }
        }

        /// <summary>
        /// Gets or the key of the set.
        /// </summary>
        /// <remarks>Key value should be unique in the collection. Returns 
        /// <see cref="Name"/> value if the Key property wasn't explicitly set.</remarks>
        public string Key
        {
            get { return string.IsNullOrEmpty(this.key) ? this.name : this.key; ; }
            set { this.key = value; }
        }

        /// <summary>
        /// Gets or sets the name of the set.
        /// </summary>
        public string Name
        {
            get { return this.name; }
            set { this.name = value; }
        }

        /// <summary>
        /// Gets or sets the description of the set.
        /// </summary>
        public string Description
        {
            get { return this.description; }
            set { this.description = value; }
        }

        // Explicit INamedValueSet implementation.

        Array INamedValueSet.Values
        {
            get { return this.Values; }
        }

        object INamedValueSet.GetSingleValue()
        {
            return GetSingleValue();
        }

        void INamedValueSet.GetInterval(out object beginValue, out object endValue)
        {
            T typedBegin, typedEnd;
            GetInterval(out typedBegin, out typedEnd);
            beginValue = typedBegin;
            endValue = typedEnd;
        }

        Array INamedValueSet.GetListOfValues()
        {
            return GetListOfValues();
        }

        #endregion

        #region Typed INamedValueSet.

        /// <summary>
        /// Gets the values contained in the current instance.
        /// </summary>
        public T[] Values
        {
            get { return this.values; }
        }

        /// <summary>
        /// Gets the value contained in the <see cref="NamedValueSetType.SingleValue"/> set type.
        /// </summary>
        /// <returns>A single value.</returns>
        /// <exception cref="InvalidOperationException">Throws a <see cref="InvalidOperationException"/> if the current 
        /// instance does not represent a <see cref="NamedValueSetType.SingleValue"/> set type.</exception>
        public T GetSingleValue()
        {
            if (this.ValueSetType != NamedValueSetType.SingleValue)
                throw new InvalidOperationException("The current set does not represent a single value.");

            if (this.Values == null || this.Values.Length == 0)
                throw new InvalidOperationException("The value is not yet set.");

            return this.Values[0];
        }

        /// <summary>
        /// Gets the begin and end values of the interval represented by the <see cref="NamedValueSetType.Interval"/> set type.
        /// </summary>
        /// <param name="beginValue">Interval begin value.</param>
        /// <param name="endValue">Interval end value.</param>
        /// <exception cref="InvalidOperationException">Throws a <see cref="InvalidOperationException"/> 
        /// if the current instance does not represent a <see cref="NamedValueSetType.Interval"/>
        /// set type or if the begin and end values are not yet set.</exception>
        public void GetInterval(out T beginValue, out T endValue)
        {
            if (this.ValueSetType != NamedValueSetType.Interval)
                throw new InvalidOperationException("The current set does not represent an interval.");

            if (this.Values == null || this.Values.Length < 2)
                throw new InvalidOperationException("The begin and end values are not yet set.");

            beginValue = this.Values[0];
            endValue = this.Values[1];
        }

        /// <summary>
        /// Gets the list of values contained in the <see cref="NamedValueSetType.ListOfValues"/> set type.
        /// </summary>
        /// <returns>Array of values.</returns>
        /// <exception cref="InvalidOperationException">Throws a <see cref="InvalidOperationException"/> 
        /// if the current instance does not represent a <see cref="NamedValueSetType.ListOfValues"/> 
        /// set type or if the list is not yet set.</exception>
        public T[] GetListOfValues()
        {
            if (this.ValueSetType != NamedValueSetType.ListOfValues)
                throw new InvalidOperationException("The current set does not represent a list of values.");

            if (this.Values == null)
                throw new InvalidOperationException("The list is not yet set.");

            return this.Values;
        }

        #endregion

        #region ICloneable.

        /// <summary>
        /// Creates a new object that is a copy of the current instance. 
        /// </summary>
        /// <returns>A new object that is a copy of this instance.</returns>
        public object Clone()
        {
            NamedValueSet<T> clone = new NamedValueSet<T>();
            CopyTo(clone);
            return clone;
        }

        /// <summary>
        /// Copies all fields of the current instance into other instance.
        /// </summary>
        /// <param name="target">Object whose fields are to be set.</param>
        private void CopyTo(NamedValueSet<T> target)
        {
            target.description = this.description;
            target.key = this.key;
            target.name = this.name;
            target.valueSetType = valueSetType;
            target.values = new T[this.values.Length];
            this.values.CopyTo(target.values, 0);
        }

        #endregion
    }
}